shhh <- suppressPackageStartupMessages # It's a library, so shhh!

shhh(library( mgcv ))
shhh(library(dplyr))
shhh(library(ggplot2))
shhh(library(lme4))
shhh(library(tidymv))
shhh(library(gamlss))
shhh(library(gsubfn))
Warning: unable to load shared object '/Library/Frameworks/R.framework/Resources/modules//R_X11.so':
  dlopen(/Library/Frameworks/R.framework/Resources/modules//R_X11.so, 0x0006): Library not loaded: '/opt/X11/lib/libSM.6.dylib'
  Referenced from: '/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/modules/R_X11.so'
  Reason: tried: '/opt/X11/lib/libSM.6.dylib' (no such file), '/usr/local/lib/libSM.6.dylib' (no such file), '/usr/lib/libSM.6.dylib' (no such file)tcltk DLL is linked to '/opt/X11/lib/libX11.6.dylib'
shhh(library(lmerTest))
shhh(library(tidyverse))
shhh(library(boot))
shhh(library(rsample))
shhh(library(plotrix))
shhh(library(ggrepel))
shhh(library(mgcv))

theme_set(theme_bw())
options(digits=4)
options(scipen=999)
set.seed(444)
pipe_message = function(.data, status) {message(status); .data}

Read in MoTR Data


rate = 160

file_prefix = "/Users/cui/Desktop/motr_provo_160/"
fnames = list.files(path=file_prefix)

df = data.frame()
for (f in fnames) {
  temp = read.csv(paste0(file_prefix, "/", f)) %>%
    mutate(subj = str_remove(f, "_reading_measures.csv")) %>%
    rename(go_past_time = go_pass_time)
  df = rbind(df, temp)
}

filter_df = df %>%
  group_by(para_nr, subj) %>%
    summarise(correct = if_else(unique(correctness) == 1, 1, 0)) %>%
  ungroup() %>%
  drop_na() %>%
  group_by(subj) %>%
    summarise(p_correct = mean(correct)) %>%
  ungroup() %>%
  mutate(p_correct = round(p_correct, digits = 2))
`summarise()` has grouped output by 'para_nr'. You can override using the `.groups` argument.
filter_df = filter_df %>%
  filter(p_correct < 0.8)
# View(filter_df)
## reader_3:0.70, reader_60:0.79, reader_76:0.72 , reader_256:0.71 , reader_262:0.57 

raw_df = df %>%
  mutate(word = str_trim(word)) %>%
  mutate(subj = str_remove(subj, "reader_")) %>%
  mutate(subj = as.integer(subj)) %>%
  filter(! subj %in% c(3, 60, 76, 256, 262)) %>%
  # See below for filtering out reading measures that are super long
  dplyr::select(expr_id, cond_id, para_nr, word, word_nr, first_duration, total_duration, gaze_duration, go_past_time, FPReg, subj) %>%
  drop_na()

raw_df
# Average across subjects
motr_agg_df = raw_df %>%
  gather(metric, value, 6:10) %>%
    group_by(para_nr, word_nr, word, metric) %>%
    # for non-binary measures, filter out that are super long
    mutate(outlier = if_else(metric != "FPReg" & value > (mean(value) + 3 * sd(value) ), T, F)) %>%
    filter(outlier == F) %>%
  # # Filter out words with a reading-time of zero
  # mutate(zero = if_else(metric != "FPReg" & value == 0, T, F)) %>%
  # filter(zero == F) %>%
  drop_na() %>%
    summarise(value = mean(value),
              nsubj = length(unique(subj))) %>%
  ungroup() %>%
  arrange(para_nr, word_nr) %>%
  rename(
    text_id = para_nr,
    word_text_idx = word_nr,
    motr_value = value
  )
`summarise()` has grouped output by 'para_nr', 'word_nr', 'word'. You can override using the `.groups` argument.
motr_agg_df
NA

Comparison to Provo

# Read in Provo surprisal, frequency and length data
provo_modeling_df = read.csv("/Users/cui/Documents/ETH/MoTR/pipeline/ancillary_data/provo_df.csv") %>%
  dplyr::select(text_id, sent_id, trigger_idx, word, freq, surp, len) %>%
  rename(word_idx = trigger_idx)

provo_modeling_df
NA
# Read in Provo eyetracking data

provo_raw_df = read.csv("/Users/cui/Documents/ETH/MoTR/pipeline/ancillary_data/provo_eyetracking.csv")

provo_eyetracking_df = provo_raw_df %>%
  dplyr::select(Participant_ID, Text_ID, Sentence_Number, Word_In_Sentence_Number, Word, Word_Number, IA_FIRST_FIX_PROGRESSIVE, IA_FIRST_RUN_DWELL_TIME, IA_DWELL_TIME, IA_REGRESSION_PATH_DURATION, IA_REGRESSION_OUT) %>%
  rename( #first_duration = IA_FIRST_FIXATION_DURATION,   
          gaze_duration = IA_FIRST_RUN_DWELL_TIME,
          total_duration = IA_DWELL_TIME,
          go_past_time = IA_REGRESSION_PATH_DURATION,
          subj = Participant_ID,
          text_id = Text_ID,
          sent_id = Sentence_Number,
          word_idx = Word_In_Sentence_Number,
          word_text_idx = Word_Number,   # IA_ID?
          word = Word,      
          FPReg = IA_REGRESSION_OUT,
          ff_progressive = IA_FIRST_FIX_PROGRESSIVE) %>%
  mutate(first_duration = gaze_duration) %>%
  mutate(gaze_duration = if_else(ff_progressive == 0, 0, gaze_duration),
         go_past_time = if_else(ff_progressive == 0, 0, go_past_time)) %>%
  dplyr::select(-ff_progressive) %>%
  gather(metric, value, 7:11) %>%
  mutate(value = if_else(is.na(value), as.integer(0), as.integer(value))) %>%
  drop_na() %>%
  mutate(word = str_trim(word)) %>%
  mutate(subj = str_remove(subj, "Sub")) %>%
  mutate(subj = as.integer(subj)) %>%
    group_by(text_id, word_text_idx, sent_id, word_idx, word, metric) %>%
    mutate(outlier = if_else(metric != "FPReg" & value > (mean(value) + 3 * sd(value) ), T, F)) %>%
    filter(outlier == F) %>%
  ungroup() #%>%
  # # Filter out words with a reading-time of zero
  # mutate(zero = if_else(metric != "FPReg" & value == 0,T, F)) %>%
  # filter(zero == F)

# Aggregate cross-participant data for all subjects
provo_eyetracking_agg_df = provo_eyetracking_df %>%
  group_by(text_id, word_text_idx, sent_id, word_idx, word, metric) %>%
    summarise(value = mean(value), .groups = 'drop') 

# Split the eyetracking data in two by subjects to see how well it correlates with itself
provo_eyetracking_subj1_df = provo_eyetracking_df %>%
  filter(subj <= 42) %>%
  group_by(text_id, word_text_idx, sent_id, word_idx, word, metric) %>%
    summarise(value = mean(value)) %>%
  ungroup() %>%
  rename(value_1 = value) %>%
  dplyr::select(-sent_id, -word_idx)
`summarise()` has grouped output by 'text_id', 'word_text_idx', 'sent_id', 'word_idx', 'word'. You can override using the `.groups` argument.
  
  provo_eyetracking_subj2_df = provo_eyetracking_df %>%
  filter(subj > 42) %>%
  group_by(text_id, word_text_idx, sent_id, word_idx, word, metric) %>%
    summarise(value = mean(value)) %>%
  ungroup() %>%
    rename(value_2 = value)%>%
  dplyr::select(-sent_id, -word_idx)
`summarise()` has grouped output by 'text_id', 'word_text_idx', 'sent_id', 'word_idx', 'word'. You can override using the `.groups` argument.
  
provo_eyetr_grouped_df = merge(provo_eyetracking_subj2_df, provo_eyetracking_subj1_df, by=c("text_id", "word_text_idx", "metric")) %>%
  filter(word.x == word.y) %>%
  dplyr::select(-word.x, -word.y) %>%
  group_by(metric) %>%
    mutate(motr_outlier = if_else(metric != "FPReg" & value_1 > (mean(value_1) + 3 * sd(value_1) ), T, F)) %>%
    filter(motr_outlier == F) %>%
    mutate(eyetr_outlier = if_else(metric != "FPReg" & value_2 > (mean(value_2) + 3 * sd(value_2) ), T, F)) %>%
    filter(eyetr_outlier == F) %>%
  ungroup() %>%
  gather(measure, value, c("value_1", "value_2")) %>%
  dplyr::select(-motr_outlier, -eyetr_outlier)
provo_df = merge(provo_eyetracking_agg_df, provo_modeling_df, by=c("text_id", "sent_id", "word_idx")) %>%
  mutate(word_text_idx = as.integer(word_text_idx - 1)) %>%
  arrange(text_id, sent_id, word_idx) %>%
  rename(eyetr_value = value) 

provo_df = merge(provo_df, motr_agg_df, by=c("text_id", "word_text_idx", "metric")) %>%
arrange(text_id, sent_id, word_idx) %>%
  # almost all the word.x != word.y is because of normalization problem, so we can keep them, instead, deleting some special cases
  filter(!(text_id == 13 & word_text_idx >= 20 & word_text_idx <= 52)) %>%
  filter(!(text_id == 3 & word_text_idx >= 46 & word_text_idx <= 57)) %>%
# # filter(word.y == word) %>%
dplyr::select(-word.x, -word.y) %>%
group_by(metric) %>%
  mutate(motr_outlier = if_else(metric != "FPReg" & motr_value > (mean(motr_value) + 3 * sd(motr_value) ), T, F)) %>%
  filter(motr_outlier == F) %>%
  mutate(eyetr_outlier = if_else(metric != "FPReg" & eyetr_value > (mean(eyetr_value) + 3 * sd(eyetr_value) ), T, F)) %>%
  filter(eyetr_outlier == F) %>%
ungroup() %>%
gather(measure, value, c("eyetr_value", "motr_value")) %>%
  dplyr::select(-motr_outlier, -eyetr_outlier)
provo_df %>%
  mutate(measure = if_else(measure == "eyetr_value", "Eyetracking Value", "MoTR Value")) %>%
  filter(metric != "FPReg") %>%
  ggplot(aes(x = value, color=metric)) +
    geom_density() +
    facet_wrap(.~measure, scales="free_y") +
    xlab("Reading Time (ms)")


# ggsave("../visualization/density.png", device = "png", width = 6, height = 2.5)
print("Gaze Duration")
[1] "Gaze Duration"
cor_df = provo_df %>% filter(metric == "gaze_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$motr_value)$estimate)
   cor 
0.7874 
cor_df = provo_eyetr_grouped_df %>% filter(metric == "gaze_duration") %>% spread(measure, value)
print(cor.test(cor_df$value_1, cor_df$value_2)$estimate)
   cor 
0.9168 
print("First Duration")
[1] "First Duration"
gd_df = provo_df %>% filter(metric == "first_duration") %>% spread(measure, value)
print(cor.test(gd_df$eyetr_value, gd_df$motr_value)$estimate)
   cor 
0.7815 
cor_df = provo_eyetr_grouped_df %>% filter(metric == "first_duration") %>% spread(measure, value)
print(cor.test(cor_df$value_1, cor_df$value_2)$estimate)
   cor 
0.9201 
print("Go Past Time")
[1] "Go Past Time"
gd_df = provo_df %>% filter(metric == "go_past_time") %>% spread(measure, value)
print(cor.test(gd_df$eyetr_value, gd_df$motr_value)$estimate)
   cor 
0.7292 
cor_df = provo_eyetr_grouped_df %>% filter(metric == "go_past_time") %>% spread(measure, value)
print(cor.test(cor_df$value_1, cor_df$value_2)$estimate)
   cor 
0.9133 
print("Total Duration")
[1] "Total Duration"
gd_df = provo_df %>% filter(metric == "total_duration") %>% spread(measure, value)
print(cor.test(gd_df$eyetr_value, gd_df$motr_value)$estimate)
   cor 
0.7601 
cor_df = provo_eyetr_grouped_df %>% filter(metric == "total_duration") %>% spread(measure, value)
print(cor.test(cor_df$value_1, cor_df$value_2)$estimate)
   cor 
0.9275 
print("Regression")
[1] "Regression"
reg_df = provo_df %>% filter(metric == "FPReg") %>% spread(measure, value)
print(cor.test(reg_df$eyetr_value, reg_df$motr_value)$estimate)
   cor 
0.3259 
cor_df = provo_eyetr_grouped_df %>% filter(metric == "FPReg") %>% group_by(text_id, metric, measure) %>%
  summarize(value = mean(value, na.rm = TRUE), .groups = 'drop') %>% spread(measure, value)
print(cor.test(cor_df$value_1, cor_df$value_2)$estimate)
   cor 
0.6807 

provo_df %>%
  filter(metric != "FPReg") %>%
  spread(measure, value) %>%
  ggplot(aes(x = motr_value, y=eyetr_value)) +
    geom_point(alpha = 0.05) +
    geom_abline(slope=1, intercept=0, color = "black") +
    #stat_summary_bin(bins=100, fun.data = "mean_cl_boot", size = 0.05) +
    facet_wrap(.~metric, scales = "free", nrow = 1) +
    coord_cartesian(ylim=c(0, 500), xlim=c(0, 500)) +
    geom_smooth()



# ggsave("../visualization/metric_cor.png", device = "png", width = 6, height = 2.5)

Correlations to Word-Level Statistical Properties

print("Gaze Duration")
[1] "Gaze Duration"
print("Len")
[1] "Len"
cor_df = provo_df %>% filter(metric == "gaze_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$len)$estimate)
   cor 
0.8597 
print(cor.test(cor_df$motr_value, cor_df$len)$estimate)
   cor 
0.8644 
print("Freq")
[1] "Freq"
cor_df = provo_df %>% filter(metric == "gaze_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$freq)$estimate)
    cor 
-0.8069 
print(cor.test(cor_df$motr_value, cor_df$freq)$estimate)
    cor 
-0.7454 
print("Surp")
[1] "Surp"
cor_df = provo_df %>% filter(metric == "gaze_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$surp)$estimate)
   cor 
0.5683 
print(cor.test(cor_df$motr_value, cor_df$surp)$estimate)
   cor 
0.4978 
print("Total Duration")
[1] "Total Duration"
print("Len")
[1] "Len"
cor_df = provo_df %>% filter(metric == "total_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$len)$estimate)
  cor 
0.825 
print(cor.test(cor_df$motr_value, cor_df$len)$estimate)
  cor 
0.838 
print("Freq")
[1] "Freq"
cor_df = provo_df %>% filter(metric == "total_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$freq)$estimate)
   cor 
-0.781 
print(cor.test(cor_df$motr_value, cor_df$freq)$estimate)
    cor 
-0.7258 
print("Surp")
[1] "Surp"
cor_df = provo_df %>% filter(metric == "total_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$surp)$estimate)
   cor 
0.5931 
print(cor.test(cor_df$motr_value, cor_df$surp)$estimate)
   cor 
0.5104 
print("First Duration")
[1] "First Duration"
print("Len")
[1] "Len"
cor_df = provo_df %>% filter(metric == "first_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$len)$estimate)
   cor 
0.8444 
print(cor.test(cor_df$motr_value, cor_df$len)$estimate)
   cor 
0.8598 
print("Freq")
[1] "Freq"
cor_df = provo_df %>% filter(metric == "first_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$freq)$estimate)
    cor 
-0.8016 
print(cor.test(cor_df$motr_value, cor_df$freq)$estimate)
    cor 
-0.7453 
print("Surp")
[1] "Surp"
cor_df = provo_df %>% filter(metric == "first_duration") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$surp)$estimate)
   cor 
0.5852 
print(cor.test(cor_df$motr_value, cor_df$surp)$estimate)
  cor 
0.507 
print("Go Past Time")
[1] "Go Past Time"
print("Len")
[1] "Len"
cor_df = provo_df %>% filter(metric == "go_past_time") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$len)$estimate)
   cor 
0.8063 
print(cor.test(cor_df$motr_value, cor_df$len)$estimate)
   cor 
0.8124 
print("Freq")
[1] "Freq"
cor_df = provo_df %>% filter(metric == "go_past_time") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$freq)$estimate)
    cor 
-0.7516 
print(cor.test(cor_df$motr_value, cor_df$freq)$estimate)
   cor 
-0.702 
print("Surp")
[1] "Surp"
cor_df = provo_df %>% filter(metric == "go_past_time") %>% spread(measure, value)
print(cor.test(cor_df$eyetr_value, cor_df$surp)$estimate)
   cor 
0.5476 
print(cor.test(cor_df$motr_value, cor_df$surp)$estimate)
   cor 
0.4893 
provo_df %>%
  gather(word_prop, word_prop_val, c("freq", "len", "surp")) %>%
  mutate(measure = if_else(measure == "eyetr_value", "Eyetracking Value", "MoTR Value")) %>%
  mutate(word_prop = case_when(
    word_prop == "freq" ~ "Frequency",
    word_prop == "len" ~ "Length",
    word_prop == "surp" ~ "Surprisal"
  )) %>%
  filter(metric == "gaze_duration") %>%
  ggplot(aes(x = value, y=word_prop_val, color = measure)) +
    geom_point(alpha = 0.1) +
    facet_wrap(measure~word_prop, scales="free", strip.position = "right") +
    geom_smooth(color = "grey") +
    xlab("Reading Measure") +
  theme(
    legend.position = "none",
    strip.placement = "outside"
  )


# ggsave("../visualization/word_prop_comps.png", device = "png", width = 6, height = 3)
provo_df %>%
  ggplot(aes(x = value, y=freq, color=metric)) +
    geom_point(alpha = 0.1) +
    facet_grid(metric~measure, scales="free") +
    geom_smooth()

provo_df %>%
  ggplot(aes(x = value, y=surp, color=metric)) +
    geom_point(alpha = 0.2) +
    facet_grid(metric~measure, scales="free") +
    geom_smooth()

Shape of surprisal / RT relationship

for current word:


fit_gam_inner = function(bootstrap_sample, mean_predictors) {
  
  df = bootstrap_sample$data
  weights = tabulate(as.integer(bootstrap_sample), nrow(df))
  
  m = gam(psychometric ~ s(surp, bs = 'cr', k = 6) + s(prev_surp, bs = 'cr', k = 6) + te(freq, len, bs = 'cr') + te(prev_freq, prev_len, bs = 'cr'), data = df, weights = weights)
  terms_to_predict = c("s(surp)", "s(prev_surp)")
  
  newdata = data.frame(surp=seq(0,20,by=0.1), prev_surp=mean_predictors$surp,
                       #surp=mean_predictors$surp, prev_surp=seq(0,20,by=0.1),
                       freq=mean_predictors$freq, prev_freq=mean_predictors$freq,
                       len=mean_predictors$len, prev_len=mean_predictors$len)

  # Returns a matrix N_samples * N_terms.
  per_term_predictions = predict(m, newdata=newdata, terms=terms_to_predict, type="terms")

  # Additive model -- sum across predictor response contributions (matrix columns).
  predictions = rowSums(per_term_predictions)

  return(newdata %>% mutate(y=predictions))
}

fit_gam = function(df, mean_predictors, alpha=0.05) {
  # Bootstrap-resample data
  boot_models = df %>% bootstraps(times=10) %>% 
   # Fit a GAM and get predictions for each sample
    mutate(smoothed=map(splits, fit_gam_inner, mean_predictors=mean_predictors))
  
  # Extract mean and 5% and 95% percentile y-values for each surprisal value
  result = boot_models %>% 
    unnest(smoothed) %>% 
    dplyr::select(surp, y) %>% 
    #dplyr::select(prev_surp, y) %>% 
    group_by(surp) %>% 
    #group_by(prev_surp) %>%
      summarise(y_lower=quantile(y, alpha / 2), 
                y_upper=quantile(y, 1 - alpha / 2),
                y=mean(y)) %>% 
    ungroup()
  
  return (result)
}

gam_modeling_df = provo_df %>%
  spread(measure, value) %>%
  # mutate(len = nchar(word)) %>%      # len has already exists, but do not count punct into len.
  group_by(metric, text_id) %>%
    arrange(word_text_idx) %>%
    mutate(prev_surp = lag(surp),
           prev_freq = lag(freq),
           prev_len = lag(len),
           prev_eyetr_value = lag(eyetr_value)) %>%
  ungroup() %>%
  drop_na() %>%
  rename(psychometric = motr_value)

smooths_df = data.frame()

metrics = c("gaze_duration", "total_duration", "go_past_time", "first_duration")
for (m in metrics) {
  print(paste0("Fitting model for ", m))
  dummy_df = gam_modeling_df %>% filter(metric == m)
  mean_predictors = dummy_df %>% summarise(surp = mean(surp), len = mean(len), freq = mean(freq))
  smooths = dummy_df %>% fit_gam(., mean_predictors)
  #Fix 0 surprisal = 0 ms
  gam_smooths = smooths %>% mutate(delta = 0 - y[1], y=y + delta, y_lower= y_lower + delta, y_upper=y_upper + delta)
  smooths_df = rbind(smooths_df, gam_smooths %>% mutate(psychometric = m))
}
[1] "Fitting model for gaze_duration"
[1] "Fitting model for total_duration"
[1] "Fitting model for go_past_time"
[1] "Fitting model for first_duration"

Get Density Data

get_d_points = function(df) {
    x = density(df$surp)$x
    y = density(df$surp)$y
    return(data.frame(x, y))
  }

density_data = data.frame()

for(m in c("gaze_duration", "total_duration", "go_past_time", "first_duration")) {
  dummy_df = provo_df %>% filter(metric == m) %>%
      do({get_d_points(.)}) %>%
      filter(x>0, x<20)
  density_data = rbind(density_data, dummy_df %>% mutate(metric=m))
}

# Surprisal curves
  ggplot() +
      # Density Data
      annotate("rect", xmin=0, xmax=20, ymin=-20,ymax=-10, fill="#f4f4f4", color="grey", alpha=1, size = 0) +
      geom_line(data = density_data, aes(x=x, y=y*50 - 18), color="#aaaaaa", size = 0.4) +
      # Surrp / Rt data
      #geom_line(data = smooths_df, aes(x=prev_surp, y=y, color = psychometric), size=0.7) +
      geom_line(data = smooths_df, aes(x=surp, y=y, color = psychometric), size=0.7) +
      #geom_ribbon(data = smooths_df, aes(x=prev_surp, ymin=y_lower, ymax=y_upper, fill = psychometric), alpha=0.3, size=0.5) +
      geom_ribbon(data = smooths_df, aes(x=surp, ymin=y_lower, ymax=y_upper, fill = psychometric), alpha=0.3, size=0.5) +
      scale_x_continuous(labels=c(0, 10, 20), breaks=c(0, 10, 20), minor_breaks = NULL) +
      facet_wrap(psychometric~., nrow = 1) +
      ylab("Slowdown due to Surprisal (ms)") +
      xlab("Surprisal of Word") +
      ggtitle("MoTR Times and Current Word Surprisal")

  theme(
    legend.position = "none",
    panel.grid.minor = element_blank()
  )
List of 2
 $ legend.position : chr "none"
 $ panel.grid.minor: list()
  ..- attr(*, "class")= chr [1:2] "element_blank" "element"
 - attr(*, "class")= chr [1:2] "theme" "gg"
 - attr(*, "complete")= logi FALSE
 - attr(*, "validate")= logi TRUE
  
# ggsave("../visualization/surprisal_rt_link.png", device = "png", width = 6, height = 2.5)

for previous word:

fit_gam_inner_2 = function(bootstrap_sample, mean_predictors) {
  
  df = bootstrap_sample$data
  weights = tabulate(as.integer(bootstrap_sample), nrow(df))
  
  m = gam(psychometric ~ s(surp, bs = 'cr', k = 6) + s(prev_surp, bs = 'cr', k = 6) + te(freq, len, bs = 'cr') + te(prev_freq, prev_len, bs = 'cr'), data = df, weights = weights)
  terms_to_predict = c("s(surp)", "s(prev_surp)")
  
  newdata = data.frame(surp=mean_predictors$surp, prev_surp=seq(0,20,by=0.1),
                       freq=mean_predictors$freq, prev_freq=mean_predictors$freq,
                       len=mean_predictors$len, prev_len=mean_predictors$len)

  # Returns a matrix N_samples * N_terms.
  per_term_predictions = predict(m, newdata=newdata, terms=terms_to_predict, type="terms")

  # Additive model -- sum across predictor response contributions (matrix columns).
  predictions = rowSums(per_term_predictions)

  return(newdata %>% mutate(y=predictions))
}

fit_gam_2 = function(df, mean_predictors, alpha=0.05) {
  # Bootstrap-resample data
  boot_models = df %>% bootstraps(times=10) %>% 
   # Fit a GAM and get predictions for each sample
    mutate(smoothed=map(splits, fit_gam_inner_2, mean_predictors=mean_predictors))
  
  # Extract mean and 5% and 95% percentile y-values for each surprisal value
  result = boot_models %>% 
    unnest(smoothed) %>% 
    dplyr::select(prev_surp, y) %>%
    group_by(prev_surp) %>%
      summarise(y_lower=quantile(y, alpha / 2), 
                y_upper=quantile(y, 1 - alpha / 2),
                y=mean(y)) %>% 
    ungroup()
  
  return (result)
}
gam_modeling_df_2 = provo_df %>%
  spread(measure, value) %>%
  # mutate(len = nchar(word)) %>%  # len has already exists, but do not count punct into len.
  group_by(metric, text_id) %>%
    arrange(word_text_idx) %>%
    mutate(prev_surp = lag(surp),
           prev_freq = lag(freq),
           prev_len = lag(len),
           prev_eyetr_value = lag(eyetr_value)) %>%
  ungroup() %>%
  drop_na() %>%
  rename(psychometric = motr_value)

smooths_df = data.frame()

metrics = c("gaze_duration", "total_duration", "go_past_time", "first_duration")
for (m in metrics) {
  print(paste0("Fitting model for ", m))
  dummy_df = gam_modeling_df_2 %>% filter(metric == m)
  mean_predictors = dummy_df %>% summarise(surp = mean(surp), len = mean(len), freq = mean(freq))
  smooths = dummy_df %>% fit_gam_2(., mean_predictors)
  #Fix 0 surprisal = 0 ms
  gam_smooths = smooths %>% mutate(delta = 0 - y[1], y=y + delta, y_lower= y_lower + delta, y_upper=y_upper + delta)
  smooths_df = rbind(smooths_df, gam_smooths %>% mutate(psychometric = m))
}
[1] "Fitting model for gaze_duration"
[1] "Fitting model for total_duration"
[1] "Fitting model for go_past_time"
[1] "Fitting model for first_duration"
get_d_points = function(df) {
    x = density(df$surp)$x
    y = density(df$surp)$y
    return(data.frame(x, y))
  }

density_data = data.frame()

for(m in c("gaze_duration", "total_duration", "go_past_time", "first_duration")) {
  dummy_df = provo_df %>% filter(metric == m) %>%
      do({get_d_points(.)}) %>%
      filter(x>0, x<20)
  density_data = rbind(density_data, dummy_df %>% mutate(metric=m))
}
# Surprisal curves
  ggplot() +
      # Density Data
      annotate("rect", xmin=0, xmax=20, ymin=-20,ymax=-10, fill="#f4f4f4", color="grey", alpha=1, size = 0) +
      geom_line(data = density_data, aes(x=x, y=y*50 - 18), color="#aaaaaa", size = 0.4) +
      # Surrp / Rt data
      geom_line(data = smooths_df, aes(x=prev_surp, y=y, color = psychometric), size=0.7) +
      geom_ribbon(data = smooths_df, aes(x=prev_surp, ymin=y_lower, ymax=y_upper, fill = psychometric), alpha=0.3, size=0.5) +
      scale_x_continuous(labels=c(0, 10, 20), breaks=c(0, 10, 20), minor_breaks = NULL) +
      facet_wrap(psychometric~., nrow = 1) +
      ylab("Slowdown due to Surprisal (ms)") +
      xlab("Surprisal of Word") +
      ggtitle("MoTR Times and Previous Word Surprisal")

  theme(
    legend.position = "none",
    panel.grid.minor = element_blank()
  )
List of 2
 $ legend.position : chr "none"
 $ panel.grid.minor: list()
  ..- attr(*, "class")= chr [1:2] "element_blank" "element"
 - attr(*, "class")= chr [1:2] "theme" "gg"
 - attr(*, "complete")= logi FALSE
 - attr(*, "validate")= logi TRUE

Precision and Recall for FPReg

FPReg_df = provo_df %>% filter(metric == "FPReg") %>% spread(measure, value)
confusion_matrix <- table(FPReg_df$motr_value > 0, FPReg_df$eyetr_value > 0)
confusion_matrix
       
        FALSE TRUE
  FALSE    79 1518
  TRUE     22  918
true_positives <- confusion_matrix[2, 2]
false_positives <- confusion_matrix[2, 1]
false_negatives <- confusion_matrix[1, 2]

precision <- true_positives / (true_positives + false_positives)
recall <- true_positives / (true_positives + false_negatives)

print("precision of Motr FPReg:")
[1] "precision of Motr FPReg:"
print(precision)
[1] 0.9766
print("Recall of Motr FPReg:")
[1] "Recall of Motr FPReg:"
print(recall)
[1] 0.3768
LS0tCnRpdGxlOiAiRXhwbG9yYXRvcnkgQW5hbHlzaXMgZm9yIE1vVFIgUmVhZGluZyBEYXRhIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0Kc2hoaCA8LSBzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMgIyBJdCdzIGEgbGlicmFyeSwgc28gc2hoaCEKCnNoaGgobGlicmFyeSggbWdjdiApKQpzaGhoKGxpYnJhcnkoZHBseXIpKQpzaGhoKGxpYnJhcnkoZ2dwbG90MikpCnNoaGgobGlicmFyeShsbWU0KSkKc2hoaChsaWJyYXJ5KHRpZHltdikpCnNoaGgobGlicmFyeShnYW1sc3MpKQpzaGhoKGxpYnJhcnkoZ3N1YmZuKSkKc2hoaChsaWJyYXJ5KGxtZXJUZXN0KSkKc2hoaChsaWJyYXJ5KHRpZHl2ZXJzZSkpCnNoaGgobGlicmFyeShib290KSkKc2hoaChsaWJyYXJ5KHJzYW1wbGUpKQpzaGhoKGxpYnJhcnkocGxvdHJpeCkpCnNoaGgobGlicmFyeShnZ3JlcGVsKSkKc2hoaChsaWJyYXJ5KG1nY3YpKQoKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCm9wdGlvbnMoZGlnaXRzPTQpCm9wdGlvbnMoc2NpcGVuPTk5OSkKc2V0LnNlZWQoNDQ0KQpwaXBlX21lc3NhZ2UgPSBmdW5jdGlvbiguZGF0YSwgc3RhdHVzKSB7bWVzc2FnZShzdGF0dXMpOyAuZGF0YX0KCmBgYAoKCiMgUmVhZCBpbiBNb1RSIERhdGEKCmBgYHtyfQoKcmF0ZSA9IDE2MAoKZmlsZV9wcmVmaXggPSAiL1VzZXJzL2N1aS9EZXNrdG9wL21vdHJfcHJvdm9fMTYwLyIKZm5hbWVzID0gbGlzdC5maWxlcyhwYXRoPWZpbGVfcHJlZml4KQoKZGYgPSBkYXRhLmZyYW1lKCkKZm9yIChmIGluIGZuYW1lcykgewogIHRlbXAgPSByZWFkLmNzdihwYXN0ZTAoZmlsZV9wcmVmaXgsICIvIiwgZikpICU+JQogICAgbXV0YXRlKHN1YmogPSBzdHJfcmVtb3ZlKGYsICJfcmVhZGluZ19tZWFzdXJlcy5jc3YiKSkgJT4lCiAgICByZW5hbWUoZ29fcGFzdF90aW1lID0gZ29fcGFzc190aW1lKQogIGRmID0gcmJpbmQoZGYsIHRlbXApCn0KCmZpbHRlcl9kZiA9IGRmICU+JQogIGdyb3VwX2J5KHBhcmFfbnIsIHN1YmopICU+JQogICAgc3VtbWFyaXNlKGNvcnJlY3QgPSBpZl9lbHNlKHVuaXF1ZShjb3JyZWN0bmVzcykgPT0gMSwgMSwgMCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgZ3JvdXBfYnkoc3ViaikgJT4lCiAgICBzdW1tYXJpc2UocF9jb3JyZWN0ID0gbWVhbihjb3JyZWN0KSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShwX2NvcnJlY3QgPSByb3VuZChwX2NvcnJlY3QsIGRpZ2l0cyA9IDIpKQoKZmlsdGVyX2RmID0gZmlsdGVyX2RmICU+JQogIGZpbHRlcihwX2NvcnJlY3QgPCAwLjgpCiMgVmlldyhmaWx0ZXJfZGYpCiMjIHJlYWRlcl8zOjAuNzAsIHJlYWRlcl82MDowLjc5LCByZWFkZXJfNzY6MC43MiAsIHJlYWRlcl8yNTY6MC43MSAsIHJlYWRlcl8yNjI6MC41NyAKCnJhd19kZiA9IGRmICU+JQogIG11dGF0ZSh3b3JkID0gc3RyX3RyaW0od29yZCkpICU+JQogIG11dGF0ZShzdWJqID0gc3RyX3JlbW92ZShzdWJqLCAicmVhZGVyXyIpKSAlPiUKICBtdXRhdGUoc3ViaiA9IGFzLmludGVnZXIoc3ViaikpICU+JQogIGZpbHRlcighIHN1YmogJWluJSBjKDMsIDYwLCA3NiwgMjU2LCAyNjIpKSAlPiUKICAjIFNlZSBiZWxvdyBmb3IgZmlsdGVyaW5nIG91dCByZWFkaW5nIG1lYXN1cmVzIHRoYXQgYXJlIHN1cGVyIGxvbmcKICBkcGx5cjo6c2VsZWN0KGV4cHJfaWQsIGNvbmRfaWQsIHBhcmFfbnIsIHdvcmQsIHdvcmRfbnIsIGZpcnN0X2R1cmF0aW9uLCB0b3RhbF9kdXJhdGlvbiwgZ2F6ZV9kdXJhdGlvbiwgZ29fcGFzdF90aW1lLCBGUFJlZywgc3ViaikgJT4lCiAgZHJvcF9uYSgpCgpyYXdfZGYKYGBgCgoKYGBge3J9CiMgQXZlcmFnZSBhY3Jvc3Mgc3ViamVjdHMKbW90cl9hZ2dfZGYgPSByYXdfZGYgJT4lCiAgZ2F0aGVyKG1ldHJpYywgdmFsdWUsIDY6MTApICU+JQogICAgZ3JvdXBfYnkocGFyYV9uciwgd29yZF9uciwgd29yZCwgbWV0cmljKSAlPiUKICAgICMgZm9yIG5vbi1iaW5hcnkgbWVhc3VyZXMsIGZpbHRlciBvdXQgdGhhdCBhcmUgc3VwZXIgbG9uZwogICAgbXV0YXRlKG91dGxpZXIgPSBpZl9lbHNlKG1ldHJpYyAhPSAiRlBSZWciICYgdmFsdWUgPiAobWVhbih2YWx1ZSkgKyAzICogc2QodmFsdWUpICksIFQsIEYpKSAlPiUKICAgIGZpbHRlcihvdXRsaWVyID09IEYpICU+JQogICMgIyBGaWx0ZXIgb3V0IHdvcmRzIHdpdGggYSByZWFkaW5nLXRpbWUgb2YgemVybwogICMgbXV0YXRlKHplcm8gPSBpZl9lbHNlKG1ldHJpYyAhPSAiRlBSZWciICYgdmFsdWUgPT0gMCwgVCwgRikpICU+JQogICMgZmlsdGVyKHplcm8gPT0gRikgJT4lCiAgZHJvcF9uYSgpICU+JQogICAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSksCiAgICAgICAgICAgICAgbnN1YmogPSBsZW5ndGgodW5pcXVlKHN1YmopKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2UocGFyYV9uciwgd29yZF9ucikgJT4lCiAgcmVuYW1lKAogICAgdGV4dF9pZCA9IHBhcmFfbnIsCiAgICB3b3JkX3RleHRfaWR4ID0gd29yZF9uciwKICAgIG1vdHJfdmFsdWUgPSB2YWx1ZQogICkKCm1vdHJfYWdnX2RmCgpgYGAKCgoKCiMgQ29tcGFyaXNvbiB0byBQcm92bwoKCmBgYHtyfQojIFJlYWQgaW4gUHJvdm8gc3VycHJpc2FsLCBmcmVxdWVuY3kgYW5kIGxlbmd0aCBkYXRhCnByb3ZvX21vZGVsaW5nX2RmID0gcmVhZC5jc3YoIi9Vc2Vycy9jdWkvRG9jdW1lbnRzL0VUSC9Nb1RSL3BpcGVsaW5lL2FuY2lsbGFyeV9kYXRhL3Byb3ZvX2RmLmNzdiIpICU+JQogIGRwbHlyOjpzZWxlY3QodGV4dF9pZCwgc2VudF9pZCwgdHJpZ2dlcl9pZHgsIHdvcmQsIGZyZXEsIHN1cnAsIGxlbikgJT4lCiAgcmVuYW1lKHdvcmRfaWR4ID0gdHJpZ2dlcl9pZHgpCgpwcm92b19tb2RlbGluZ19kZgoKYGBgCgpgYGB7cn0KIyBSZWFkIGluIFByb3ZvIGV5ZXRyYWNraW5nIGRhdGEKCnByb3ZvX3Jhd19kZiA9IHJlYWQuY3N2KCIvVXNlcnMvY3VpL0RvY3VtZW50cy9FVEgvTW9UUi9waXBlbGluZS9hbmNpbGxhcnlfZGF0YS9wcm92b19leWV0cmFja2luZy5jc3YiKQoKcHJvdm9fZXlldHJhY2tpbmdfZGYgPSBwcm92b19yYXdfZGYgJT4lCiAgZHBseXI6OnNlbGVjdChQYXJ0aWNpcGFudF9JRCwgVGV4dF9JRCwgU2VudGVuY2VfTnVtYmVyLCBXb3JkX0luX1NlbnRlbmNlX051bWJlciwgV29yZCwgV29yZF9OdW1iZXIsIElBX0ZJUlNUX0ZJWF9QUk9HUkVTU0lWRSwgSUFfRklSU1RfUlVOX0RXRUxMX1RJTUUsIElBX0RXRUxMX1RJTUUsIElBX1JFR1JFU1NJT05fUEFUSF9EVVJBVElPTiwgSUFfUkVHUkVTU0lPTl9PVVQpICU+JQogIHJlbmFtZSggI2ZpcnN0X2R1cmF0aW9uID0gSUFfRklSU1RfRklYQVRJT05fRFVSQVRJT04sICAgCiAgICAgICAgICBnYXplX2R1cmF0aW9uID0gSUFfRklSU1RfUlVOX0RXRUxMX1RJTUUsCiAgICAgICAgICB0b3RhbF9kdXJhdGlvbiA9IElBX0RXRUxMX1RJTUUsCiAgICAgICAgICBnb19wYXN0X3RpbWUgPSBJQV9SRUdSRVNTSU9OX1BBVEhfRFVSQVRJT04sCiAgICAgICAgICBzdWJqID0gUGFydGljaXBhbnRfSUQsCiAgICAgICAgICB0ZXh0X2lkID0gVGV4dF9JRCwKICAgICAgICAgIHNlbnRfaWQgPSBTZW50ZW5jZV9OdW1iZXIsCiAgICAgICAgICB3b3JkX2lkeCA9IFdvcmRfSW5fU2VudGVuY2VfTnVtYmVyLAogICAgICAgICAgd29yZF90ZXh0X2lkeCA9IFdvcmRfTnVtYmVyLCAgICMgSUFfSUQ/CiAgICAgICAgICB3b3JkID0gV29yZCwgICAgICAKICAgICAgICAgIEZQUmVnID0gSUFfUkVHUkVTU0lPTl9PVVQsCiAgICAgICAgICBmZl9wcm9ncmVzc2l2ZSA9IElBX0ZJUlNUX0ZJWF9QUk9HUkVTU0lWRSkgJT4lCiAgbXV0YXRlKGZpcnN0X2R1cmF0aW9uID0gZ2F6ZV9kdXJhdGlvbikgJT4lCiAgbXV0YXRlKGdhemVfZHVyYXRpb24gPSBpZl9lbHNlKGZmX3Byb2dyZXNzaXZlID09IDAsIDAsIGdhemVfZHVyYXRpb24pLAogICAgICAgICBnb19wYXN0X3RpbWUgPSBpZl9lbHNlKGZmX3Byb2dyZXNzaXZlID09IDAsIDAsIGdvX3Bhc3RfdGltZSkpICU+JQogIGRwbHlyOjpzZWxlY3QoLWZmX3Byb2dyZXNzaXZlKSAlPiUKICBnYXRoZXIobWV0cmljLCB2YWx1ZSwgNzoxMSkgJT4lCiAgbXV0YXRlKHZhbHVlID0gaWZfZWxzZShpcy5uYSh2YWx1ZSksIGFzLmludGVnZXIoMCksIGFzLmludGVnZXIodmFsdWUpKSkgJT4lCiAgZHJvcF9uYSgpICU+JQogIG11dGF0ZSh3b3JkID0gc3RyX3RyaW0od29yZCkpICU+JQogIG11dGF0ZShzdWJqID0gc3RyX3JlbW92ZShzdWJqLCAiU3ViIikpICU+JQogIG11dGF0ZShzdWJqID0gYXMuaW50ZWdlcihzdWJqKSkgJT4lCiAgICBncm91cF9ieSh0ZXh0X2lkLCB3b3JkX3RleHRfaWR4LCBzZW50X2lkLCB3b3JkX2lkeCwgd29yZCwgbWV0cmljKSAlPiUKICAgIG11dGF0ZShvdXRsaWVyID0gaWZfZWxzZShtZXRyaWMgIT0gIkZQUmVnIiAmIHZhbHVlID4gKG1lYW4odmFsdWUpICsgMyAqIHNkKHZhbHVlKSApLCBULCBGKSkgJT4lCiAgICBmaWx0ZXIob3V0bGllciA9PSBGKSAlPiUKICB1bmdyb3VwKCkgIyU+JQogICMgIyBGaWx0ZXIgb3V0IHdvcmRzIHdpdGggYSByZWFkaW5nLXRpbWUgb2YgemVybwogICMgbXV0YXRlKHplcm8gPSBpZl9lbHNlKG1ldHJpYyAhPSAiRlBSZWciICYgdmFsdWUgPT0gMCxULCBGKSkgJT4lCiAgIyBmaWx0ZXIoemVybyA9PSBGKQoKIyBBZ2dyZWdhdGUgY3Jvc3MtcGFydGljaXBhbnQgZGF0YSBmb3IgYWxsIHN1YmplY3RzCnByb3ZvX2V5ZXRyYWNraW5nX2FnZ19kZiA9IHByb3ZvX2V5ZXRyYWNraW5nX2RmICU+JQogIGdyb3VwX2J5KHRleHRfaWQsIHdvcmRfdGV4dF9pZHgsIHNlbnRfaWQsIHdvcmRfaWR4LCB3b3JkLCBtZXRyaWMpICU+JQogICAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSksIC5ncm91cHMgPSAnZHJvcCcpIAoKCmBgYAoKYGBge3J9CgojIFNwbGl0IHRoZSBleWV0cmFja2luZyBkYXRhIGluIHR3byBieSBzdWJqZWN0cyB0byBzZWUgaG93IHdlbGwgaXQgY29ycmVsYXRlcyB3aXRoIGl0c2VsZgpwcm92b19leWV0cmFja2luZ19zdWJqMV9kZiA9IHByb3ZvX2V5ZXRyYWNraW5nX2RmICU+JQogIGZpbHRlcihzdWJqIDw9IDQyKSAlPiUKICBncm91cF9ieSh0ZXh0X2lkLCB3b3JkX3RleHRfaWR4LCBzZW50X2lkLCB3b3JkX2lkeCwgd29yZCwgbWV0cmljKSAlPiUKICAgIHN1bW1hcmlzZSh2YWx1ZSA9IG1lYW4odmFsdWUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgcmVuYW1lKHZhbHVlXzEgPSB2YWx1ZSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtc2VudF9pZCwgLXdvcmRfaWR4KQogIAogIHByb3ZvX2V5ZXRyYWNraW5nX3N1YmoyX2RmID0gcHJvdm9fZXlldHJhY2tpbmdfZGYgJT4lCiAgZmlsdGVyKHN1YmogPiA0MikgJT4lCiAgZ3JvdXBfYnkodGV4dF9pZCwgd29yZF90ZXh0X2lkeCwgc2VudF9pZCwgd29yZF9pZHgsIHdvcmQsIG1ldHJpYykgJT4lCiAgICBzdW1tYXJpc2UodmFsdWUgPSBtZWFuKHZhbHVlKSkgJT4lCiAgdW5ncm91cCgpICU+JQogICAgcmVuYW1lKHZhbHVlXzIgPSB2YWx1ZSklPiUKICBkcGx5cjo6c2VsZWN0KC1zZW50X2lkLCAtd29yZF9pZHgpCiAgCnByb3ZvX2V5ZXRyX2dyb3VwZWRfZGYgPSBtZXJnZShwcm92b19leWV0cmFja2luZ19zdWJqMl9kZiwgcHJvdm9fZXlldHJhY2tpbmdfc3ViajFfZGYsIGJ5PWMoInRleHRfaWQiLCAid29yZF90ZXh0X2lkeCIsICJtZXRyaWMiKSkgJT4lCiAgZmlsdGVyKHdvcmQueCA9PSB3b3JkLnkpICU+JQogIGRwbHlyOjpzZWxlY3QoLXdvcmQueCwgLXdvcmQueSkgJT4lCiAgZ3JvdXBfYnkobWV0cmljKSAlPiUKICAgIG11dGF0ZShtb3RyX291dGxpZXIgPSBpZl9lbHNlKG1ldHJpYyAhPSAiRlBSZWciICYgdmFsdWVfMSA+IChtZWFuKHZhbHVlXzEpICsgMyAqIHNkKHZhbHVlXzEpICksIFQsIEYpKSAlPiUKICAgIGZpbHRlcihtb3RyX291dGxpZXIgPT0gRikgJT4lCiAgICBtdXRhdGUoZXlldHJfb3V0bGllciA9IGlmX2Vsc2UobWV0cmljICE9ICJGUFJlZyIgJiB2YWx1ZV8yID4gKG1lYW4odmFsdWVfMikgKyAzICogc2QodmFsdWVfMikgKSwgVCwgRikpICU+JQogICAgZmlsdGVyKGV5ZXRyX291dGxpZXIgPT0gRikgJT4lCiAgdW5ncm91cCgpICU+JQogIGdhdGhlcihtZWFzdXJlLCB2YWx1ZSwgYygidmFsdWVfMSIsICJ2YWx1ZV8yIikpICU+JQogIGRwbHlyOjpzZWxlY3QoLW1vdHJfb3V0bGllciwgLWV5ZXRyX291dGxpZXIpCgoKYGBgCgoKYGBge3J9CnByb3ZvX2RmID0gbWVyZ2UocHJvdm9fZXlldHJhY2tpbmdfYWdnX2RmLCBwcm92b19tb2RlbGluZ19kZiwgYnk9YygidGV4dF9pZCIsICJzZW50X2lkIiwgIndvcmRfaWR4IikpICU+JQogIG11dGF0ZSh3b3JkX3RleHRfaWR4ID0gYXMuaW50ZWdlcih3b3JkX3RleHRfaWR4IC0gMSkpICU+JQogIGFycmFuZ2UodGV4dF9pZCwgc2VudF9pZCwgd29yZF9pZHgpICU+JQogIHJlbmFtZShleWV0cl92YWx1ZSA9IHZhbHVlKSAKCnByb3ZvX2RmID0gbWVyZ2UocHJvdm9fZGYsIG1vdHJfYWdnX2RmLCBieT1jKCJ0ZXh0X2lkIiwgIndvcmRfdGV4dF9pZHgiLCAibWV0cmljIikpICU+JQphcnJhbmdlKHRleHRfaWQsIHNlbnRfaWQsIHdvcmRfaWR4KSAlPiUKICAjIGFsbW9zdCBhbGwgdGhlIHdvcmQueCAhPSB3b3JkLnkgaXMgYmVjYXVzZSBvZiBub3JtYWxpemF0aW9uIHByb2JsZW0sIHNvIHdlIGNhbiBrZWVwIHRoZW0sIGluc3RlYWQsIGRlbGV0aW5nIHNvbWUgc3BlY2lhbCBjYXNlcwogIGZpbHRlcighKHRleHRfaWQgPT0gMTMgJiB3b3JkX3RleHRfaWR4ID49IDIwICYgd29yZF90ZXh0X2lkeCA8PSA1MikpICU+JQogIGZpbHRlcighKHRleHRfaWQgPT0gMyAmIHdvcmRfdGV4dF9pZHggPj0gNDYgJiB3b3JkX3RleHRfaWR4IDw9IDU3KSkgJT4lCiMgIyBmaWx0ZXIod29yZC55ID09IHdvcmQpICU+JQpkcGx5cjo6c2VsZWN0KC13b3JkLngsIC13b3JkLnkpICU+JQpncm91cF9ieShtZXRyaWMpICU+JQogIG11dGF0ZShtb3RyX291dGxpZXIgPSBpZl9lbHNlKG1ldHJpYyAhPSAiRlBSZWciICYgbW90cl92YWx1ZSA+IChtZWFuKG1vdHJfdmFsdWUpICsgMyAqIHNkKG1vdHJfdmFsdWUpICksIFQsIEYpKSAlPiUKICBmaWx0ZXIobW90cl9vdXRsaWVyID09IEYpICU+JQogIG11dGF0ZShleWV0cl9vdXRsaWVyID0gaWZfZWxzZShtZXRyaWMgIT0gIkZQUmVnIiAmIGV5ZXRyX3ZhbHVlID4gKG1lYW4oZXlldHJfdmFsdWUpICsgMyAqIHNkKGV5ZXRyX3ZhbHVlKSApLCBULCBGKSkgJT4lCiAgZmlsdGVyKGV5ZXRyX291dGxpZXIgPT0gRikgJT4lCnVuZ3JvdXAoKSAlPiUKZ2F0aGVyKG1lYXN1cmUsIHZhbHVlLCBjKCJleWV0cl92YWx1ZSIsICJtb3RyX3ZhbHVlIikpICU+JQogIGRwbHlyOjpzZWxlY3QoLW1vdHJfb3V0bGllciwgLWV5ZXRyX291dGxpZXIpCgoKYGBgCgpgYGB7cn0KcHJvdm9fZGYgJT4lCiAgbXV0YXRlKG1lYXN1cmUgPSBpZl9lbHNlKG1lYXN1cmUgPT0gImV5ZXRyX3ZhbHVlIiwgIkV5ZXRyYWNraW5nIFZhbHVlIiwgIk1vVFIgVmFsdWUiKSkgJT4lCiAgZmlsdGVyKG1ldHJpYyAhPSAiRlBSZWciKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgY29sb3I9bWV0cmljKSkgKwogICAgZ2VvbV9kZW5zaXR5KCkgKwogICAgZmFjZXRfd3JhcCgufm1lYXN1cmUsIHNjYWxlcz0iZnJlZV95IikgKwogICAgeGxhYigiUmVhZGluZyBUaW1lIChtcykiKQoKIyBnZ3NhdmUoIi4uL3Zpc3VhbGl6YXRpb24vZGVuc2l0eS5wbmciLCBkZXZpY2UgPSAicG5nIiwgd2lkdGggPSA2LCBoZWlnaHQgPSAyLjUpCmBgYAoKCmBgYHtyfQpwcmludCgiR2F6ZSBEdXJhdGlvbiIpCmNvcl9kZiA9IHByb3ZvX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJnYXplX2R1cmF0aW9uIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJGV5ZXRyX3ZhbHVlLCBjb3JfZGYkbW90cl92YWx1ZSkkZXN0aW1hdGUpCgpjb3JfZGYgPSBwcm92b19leWV0cl9ncm91cGVkX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJnYXplX2R1cmF0aW9uIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJHZhbHVlXzEsIGNvcl9kZiR2YWx1ZV8yKSRlc3RpbWF0ZSkKCnByaW50KCJGaXJzdCBEdXJhdGlvbiIpCmdkX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gImZpcnN0X2R1cmF0aW9uIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoZ2RfZGYkZXlldHJfdmFsdWUsIGdkX2RmJG1vdHJfdmFsdWUpJGVzdGltYXRlKQoKY29yX2RmID0gcHJvdm9fZXlldHJfZ3JvdXBlZF9kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiZmlyc3RfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkdmFsdWVfMSwgY29yX2RmJHZhbHVlXzIpJGVzdGltYXRlKQoKcHJpbnQoIkdvIFBhc3QgVGltZSIpCgpnZF9kZiA9IHByb3ZvX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJnb19wYXN0X3RpbWUiKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChnZF9kZiRleWV0cl92YWx1ZSwgZ2RfZGYkbW90cl92YWx1ZSkkZXN0aW1hdGUpCgpjb3JfZGYgPSBwcm92b19leWV0cl9ncm91cGVkX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJnb19wYXN0X3RpbWUiKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkdmFsdWVfMSwgY29yX2RmJHZhbHVlXzIpJGVzdGltYXRlKQoKcHJpbnQoIlRvdGFsIER1cmF0aW9uIikKCmdkX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gInRvdGFsX2R1cmF0aW9uIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoZ2RfZGYkZXlldHJfdmFsdWUsIGdkX2RmJG1vdHJfdmFsdWUpJGVzdGltYXRlKQoKY29yX2RmID0gcHJvdm9fZXlldHJfZ3JvdXBlZF9kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAidG90YWxfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkdmFsdWVfMSwgY29yX2RmJHZhbHVlXzIpJGVzdGltYXRlKQoKcHJpbnQoIlJlZ3Jlc3Npb24iKQoKcmVnX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gIkZQUmVnIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QocmVnX2RmJGV5ZXRyX3ZhbHVlLCByZWdfZGYkbW90cl92YWx1ZSkkZXN0aW1hdGUpCgpjb3JfZGYgPSBwcm92b19leWV0cl9ncm91cGVkX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJGUFJlZyIpICU+JSBncm91cF9ieSh0ZXh0X2lkLCBtZXRyaWMsIG1lYXN1cmUpICU+JQogIHN1bW1hcml6ZSh2YWx1ZSA9IG1lYW4odmFsdWUsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAnZHJvcCcpICU+JSBzcHJlYWQobWVhc3VyZSwgdmFsdWUpCnByaW50KGNvci50ZXN0KGNvcl9kZiR2YWx1ZV8xLCBjb3JfZGYkdmFsdWVfMikkZXN0aW1hdGUpCgpgYGAKCgoKYGBge3J9Cgpwcm92b19kZiAlPiUKICBmaWx0ZXIobWV0cmljICE9ICJGUFJlZyIpICU+JQogIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbW90cl92YWx1ZSwgeT1leWV0cl92YWx1ZSkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjA1KSArCiAgICBnZW9tX2FibGluZShzbG9wZT0xLCBpbnRlcmNlcHQ9MCwgY29sb3IgPSAiYmxhY2siKSArCiAgICAjc3RhdF9zdW1tYXJ5X2JpbihiaW5zPTEwMCwgZnVuLmRhdGEgPSAibWVhbl9jbF9ib290Iiwgc2l6ZSA9IDAuMDUpICsKICAgIGZhY2V0X3dyYXAoLn5tZXRyaWMsIHNjYWxlcyA9ICJmcmVlIiwgbnJvdyA9IDEpICsKICAgIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoMCwgNTAwKSwgeGxpbT1jKDAsIDUwMCkpICsKICAgIGdlb21fc21vb3RoKCkKCgojIGdnc2F2ZSgiLi4vdmlzdWFsaXphdGlvbi9tZXRyaWNfY29yLnBuZyIsIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkKYGBgCiMjIENvcnJlbGF0aW9ucyB0byBXb3JkLUxldmVsIFN0YXRpc3RpY2FsIFByb3BlcnRpZXMKCmBgYHtyfQpwcmludCgiR2F6ZSBEdXJhdGlvbiIpCnByaW50KCJMZW4iKQpjb3JfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiZ2F6ZV9kdXJhdGlvbiIpICU+JSBzcHJlYWQobWVhc3VyZSwgdmFsdWUpCnByaW50KGNvci50ZXN0KGNvcl9kZiRleWV0cl92YWx1ZSwgY29yX2RmJGxlbikkZXN0aW1hdGUpCnByaW50KGNvci50ZXN0KGNvcl9kZiRtb3RyX3ZhbHVlLCBjb3JfZGYkbGVuKSRlc3RpbWF0ZSkKCnByaW50KCJGcmVxIikKY29yX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gImdhemVfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkZXlldHJfdmFsdWUsIGNvcl9kZiRmcmVxKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRmcmVxKSRlc3RpbWF0ZSkKCnByaW50KCJTdXJwIikKY29yX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gImdhemVfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkZXlldHJfdmFsdWUsIGNvcl9kZiRzdXJwKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRzdXJwKSRlc3RpbWF0ZSkKCgpgYGAKCmBgYHtyfQpwcmludCgiVG90YWwgRHVyYXRpb24iKQpwcmludCgiTGVuIikKY29yX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gInRvdGFsX2R1cmF0aW9uIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJGV5ZXRyX3ZhbHVlLCBjb3JfZGYkbGVuKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRsZW4pJGVzdGltYXRlKQoKcHJpbnQoIkZyZXEiKQpjb3JfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAidG90YWxfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkZXlldHJfdmFsdWUsIGNvcl9kZiRmcmVxKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRmcmVxKSRlc3RpbWF0ZSkKCnByaW50KCJTdXJwIikKY29yX2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gInRvdGFsX2R1cmF0aW9uIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJGV5ZXRyX3ZhbHVlLCBjb3JfZGYkc3VycCkkZXN0aW1hdGUpCnByaW50KGNvci50ZXN0KGNvcl9kZiRtb3RyX3ZhbHVlLCBjb3JfZGYkc3VycCkkZXN0aW1hdGUpCmBgYAoKYGBge3J9CnByaW50KCJGaXJzdCBEdXJhdGlvbiIpCnByaW50KCJMZW4iKQpjb3JfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiZmlyc3RfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkZXlldHJfdmFsdWUsIGNvcl9kZiRsZW4pJGVzdGltYXRlKQpwcmludChjb3IudGVzdChjb3JfZGYkbW90cl92YWx1ZSwgY29yX2RmJGxlbikkZXN0aW1hdGUpCgpwcmludCgiRnJlcSIpCmNvcl9kZiA9IHByb3ZvX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJmaXJzdF9kdXJhdGlvbiIpICU+JSBzcHJlYWQobWVhc3VyZSwgdmFsdWUpCnByaW50KGNvci50ZXN0KGNvcl9kZiRleWV0cl92YWx1ZSwgY29yX2RmJGZyZXEpJGVzdGltYXRlKQpwcmludChjb3IudGVzdChjb3JfZGYkbW90cl92YWx1ZSwgY29yX2RmJGZyZXEpJGVzdGltYXRlKQoKcHJpbnQoIlN1cnAiKQpjb3JfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiZmlyc3RfZHVyYXRpb24iKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkZXlldHJfdmFsdWUsIGNvcl9kZiRzdXJwKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRzdXJwKSRlc3RpbWF0ZSkKYGBgCgpgYGB7cn0KcHJpbnQoIkdvIFBhc3QgVGltZSIpCnByaW50KCJMZW4iKQpjb3JfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiZ29fcGFzdF90aW1lIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJGV5ZXRyX3ZhbHVlLCBjb3JfZGYkbGVuKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRsZW4pJGVzdGltYXRlKQoKcHJpbnQoIkZyZXEiKQpjb3JfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiZ29fcGFzdF90aW1lIikgJT4lIHNwcmVhZChtZWFzdXJlLCB2YWx1ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJGV5ZXRyX3ZhbHVlLCBjb3JfZGYkZnJlcSkkZXN0aW1hdGUpCnByaW50KGNvci50ZXN0KGNvcl9kZiRtb3RyX3ZhbHVlLCBjb3JfZGYkZnJlcSkkZXN0aW1hdGUpCgpwcmludCgiU3VycCIpCmNvcl9kZiA9IHByb3ZvX2RmICU+JSBmaWx0ZXIobWV0cmljID09ICJnb19wYXN0X3RpbWUiKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpwcmludChjb3IudGVzdChjb3JfZGYkZXlldHJfdmFsdWUsIGNvcl9kZiRzdXJwKSRlc3RpbWF0ZSkKcHJpbnQoY29yLnRlc3QoY29yX2RmJG1vdHJfdmFsdWUsIGNvcl9kZiRzdXJwKSRlc3RpbWF0ZSkKYGBgCgpgYGB7cn0KcHJvdm9fZGYgJT4lCiAgZ2F0aGVyKHdvcmRfcHJvcCwgd29yZF9wcm9wX3ZhbCwgYygiZnJlcSIsICJsZW4iLCAic3VycCIpKSAlPiUKICBtdXRhdGUobWVhc3VyZSA9IGlmX2Vsc2UobWVhc3VyZSA9PSAiZXlldHJfdmFsdWUiLCAiRXlldHJhY2tpbmcgVmFsdWUiLCAiTW9UUiBWYWx1ZSIpKSAlPiUKICBtdXRhdGUod29yZF9wcm9wID0gY2FzZV93aGVuKAogICAgd29yZF9wcm9wID09ICJmcmVxIiB+ICJGcmVxdWVuY3kiLAogICAgd29yZF9wcm9wID09ICJsZW4iIH4gIkxlbmd0aCIsCiAgICB3b3JkX3Byb3AgPT0gInN1cnAiIH4gIlN1cnByaXNhbCIKICApKSAlPiUKICBmaWx0ZXIobWV0cmljID09ICJnYXplX2R1cmF0aW9uIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIHk9d29yZF9wcm9wX3ZhbCwgY29sb3IgPSBtZWFzdXJlKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKwogICAgZmFjZXRfd3JhcChtZWFzdXJlfndvcmRfcHJvcCwgc2NhbGVzPSJmcmVlIiwgc3RyaXAucG9zaXRpb24gPSAicmlnaHQiKSArCiAgICBnZW9tX3Ntb290aChjb2xvciA9ICJncmV5IikgKwogICAgeGxhYigiUmVhZGluZyBNZWFzdXJlIikgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgc3RyaXAucGxhY2VtZW50ID0gIm91dHNpZGUiCiAgKQoKIyBnZ3NhdmUoIi4uL3Zpc3VhbGl6YXRpb24vd29yZF9wcm9wX2NvbXBzLnBuZyIsIGRldmljZSA9ICJwbmciLCB3aWR0aCA9IDYsIGhlaWdodCA9IDMpCgpgYGAKCmBgYHtyfQpwcm92b19kZiAlPiUKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgeT1mcmVxLCBjb2xvcj1tZXRyaWMpKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArCiAgICBmYWNldF9ncmlkKG1ldHJpY35tZWFzdXJlLCBzY2FsZXM9ImZyZWUiKSArCiAgICBnZW9tX3Ntb290aCgpCmBgYAoKYGBge3J9CnByb3ZvX2RmICU+JQogIGdncGxvdChhZXMoeCA9IHZhbHVlLCB5PXN1cnAsIGNvbG9yPW1ldHJpYykpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICsKICAgIGZhY2V0X2dyaWQobWV0cmljfm1lYXN1cmUsIHNjYWxlcz0iZnJlZSIpICsKICAgIGdlb21fc21vb3RoKCkKYGBgCgoKIyMgU2hhcGUgb2Ygc3VycHJpc2FsIC8gUlQgcmVsYXRpb25zaGlwCgojIyMgZm9yIGN1cnJlbnQgd29yZDoKYGBge3J9CgpmaXRfZ2FtX2lubmVyID0gZnVuY3Rpb24oYm9vdHN0cmFwX3NhbXBsZSwgbWVhbl9wcmVkaWN0b3JzKSB7CiAgCiAgZGYgPSBib290c3RyYXBfc2FtcGxlJGRhdGEKICB3ZWlnaHRzID0gdGFidWxhdGUoYXMuaW50ZWdlcihib290c3RyYXBfc2FtcGxlKSwgbnJvdyhkZikpCiAgCiAgbSA9IGdhbShwc3ljaG9tZXRyaWMgfiBzKHN1cnAsIGJzID0gJ2NyJywgayA9IDYpICsgcyhwcmV2X3N1cnAsIGJzID0gJ2NyJywgayA9IDYpICsgdGUoZnJlcSwgbGVuLCBicyA9ICdjcicpICsgdGUocHJldl9mcmVxLCBwcmV2X2xlbiwgYnMgPSAnY3InKSwgZGF0YSA9IGRmLCB3ZWlnaHRzID0gd2VpZ2h0cykKICB0ZXJtc190b19wcmVkaWN0ID0gYygicyhzdXJwKSIsICJzKHByZXZfc3VycCkiKQogIAogIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHN1cnA9c2VxKDAsMjAsYnk9MC4xKSwgcHJldl9zdXJwPW1lYW5fcHJlZGljdG9ycyRzdXJwLAogICAgICAgICAgICAgICAgICAgICAgICNzdXJwPW1lYW5fcHJlZGljdG9ycyRzdXJwLCBwcmV2X3N1cnA9c2VxKDAsMjAsYnk9MC4xKSwKICAgICAgICAgICAgICAgICAgICAgICBmcmVxPW1lYW5fcHJlZGljdG9ycyRmcmVxLCBwcmV2X2ZyZXE9bWVhbl9wcmVkaWN0b3JzJGZyZXEsCiAgICAgICAgICAgICAgICAgICAgICAgbGVuPW1lYW5fcHJlZGljdG9ycyRsZW4sIHByZXZfbGVuPW1lYW5fcHJlZGljdG9ycyRsZW4pCgogICMgUmV0dXJucyBhIG1hdHJpeCBOX3NhbXBsZXMgKiBOX3Rlcm1zLgogIHBlcl90ZXJtX3ByZWRpY3Rpb25zID0gcHJlZGljdChtLCBuZXdkYXRhPW5ld2RhdGEsIHRlcm1zPXRlcm1zX3RvX3ByZWRpY3QsIHR5cGU9InRlcm1zIikKCiAgIyBBZGRpdGl2ZSBtb2RlbCAtLSBzdW0gYWNyb3NzIHByZWRpY3RvciByZXNwb25zZSBjb250cmlidXRpb25zIChtYXRyaXggY29sdW1ucykuCiAgcHJlZGljdGlvbnMgPSByb3dTdW1zKHBlcl90ZXJtX3ByZWRpY3Rpb25zKQoKICByZXR1cm4obmV3ZGF0YSAlPiUgbXV0YXRlKHk9cHJlZGljdGlvbnMpKQp9CgpmaXRfZ2FtID0gZnVuY3Rpb24oZGYsIG1lYW5fcHJlZGljdG9ycywgYWxwaGE9MC4wNSkgewogICMgQm9vdHN0cmFwLXJlc2FtcGxlIGRhdGEKICBib290X21vZGVscyA9IGRmICU+JSBib290c3RyYXBzKHRpbWVzPTEwKSAlPiUgCiAgICMgRml0IGEgR0FNIGFuZCBnZXQgcHJlZGljdGlvbnMgZm9yIGVhY2ggc2FtcGxlCiAgICBtdXRhdGUoc21vb3RoZWQ9bWFwKHNwbGl0cywgZml0X2dhbV9pbm5lciwgbWVhbl9wcmVkaWN0b3JzPW1lYW5fcHJlZGljdG9ycykpCiAgCiAgIyBFeHRyYWN0IG1lYW4gYW5kIDUlIGFuZCA5NSUgcGVyY2VudGlsZSB5LXZhbHVlcyBmb3IgZWFjaCBzdXJwcmlzYWwgdmFsdWUKICByZXN1bHQgPSBib290X21vZGVscyAlPiUgCiAgICB1bm5lc3Qoc21vb3RoZWQpICU+JSAKICAgIGRwbHlyOjpzZWxlY3Qoc3VycCwgeSkgJT4lIAogICAgI2RwbHlyOjpzZWxlY3QocHJldl9zdXJwLCB5KSAlPiUgCiAgICBncm91cF9ieShzdXJwKSAlPiUgCiAgICAjZ3JvdXBfYnkocHJldl9zdXJwKSAlPiUKICAgICAgc3VtbWFyaXNlKHlfbG93ZXI9cXVhbnRpbGUoeSwgYWxwaGEgLyAyKSwgCiAgICAgICAgICAgICAgICB5X3VwcGVyPXF1YW50aWxlKHksIDEgLSBhbHBoYSAvIDIpLAogICAgICAgICAgICAgICAgeT1tZWFuKHkpKSAlPiUgCiAgICB1bmdyb3VwKCkKICAKICByZXR1cm4gKHJlc3VsdCkKfQoKYGBgCgoKYGBge3J9CgpnYW1fbW9kZWxpbmdfZGYgPSBwcm92b19kZiAlPiUKICBzcHJlYWQobWVhc3VyZSwgdmFsdWUpICU+JQogICMgbXV0YXRlKGxlbiA9IG5jaGFyKHdvcmQpKSAlPiUgICAgICAjIGxlbiBoYXMgYWxyZWFkeSBleGlzdHMsIGJ1dCBkbyBub3QgY291bnQgcHVuY3QgaW50byBsZW4uCiAgZ3JvdXBfYnkobWV0cmljLCB0ZXh0X2lkKSAlPiUKICAgIGFycmFuZ2Uod29yZF90ZXh0X2lkeCkgJT4lCiAgICBtdXRhdGUocHJldl9zdXJwID0gbGFnKHN1cnApLAogICAgICAgICAgIHByZXZfZnJlcSA9IGxhZyhmcmVxKSwKICAgICAgICAgICBwcmV2X2xlbiA9IGxhZyhsZW4pLAogICAgICAgICAgIHByZXZfZXlldHJfdmFsdWUgPSBsYWcoZXlldHJfdmFsdWUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZHJvcF9uYSgpICU+JQogIHJlbmFtZShwc3ljaG9tZXRyaWMgPSBtb3RyX3ZhbHVlKQoKc21vb3Roc19kZiA9IGRhdGEuZnJhbWUoKQoKbWV0cmljcyA9IGMoImdhemVfZHVyYXRpb24iLCAidG90YWxfZHVyYXRpb24iLCAiZ29fcGFzdF90aW1lIiwgImZpcnN0X2R1cmF0aW9uIikKZm9yIChtIGluIG1ldHJpY3MpIHsKICBwcmludChwYXN0ZTAoIkZpdHRpbmcgbW9kZWwgZm9yICIsIG0pKQogIGR1bW15X2RmID0gZ2FtX21vZGVsaW5nX2RmICU+JSBmaWx0ZXIobWV0cmljID09IG0pCiAgbWVhbl9wcmVkaWN0b3JzID0gZHVtbXlfZGYgJT4lIHN1bW1hcmlzZShzdXJwID0gbWVhbihzdXJwKSwgbGVuID0gbWVhbihsZW4pLCBmcmVxID0gbWVhbihmcmVxKSkKICBzbW9vdGhzID0gZHVtbXlfZGYgJT4lIGZpdF9nYW0oLiwgbWVhbl9wcmVkaWN0b3JzKQogICNGaXggMCBzdXJwcmlzYWwgPSAwIG1zCiAgZ2FtX3Ntb290aHMgPSBzbW9vdGhzICU+JSBtdXRhdGUoZGVsdGEgPSAwIC0geVsxXSwgeT15ICsgZGVsdGEsIHlfbG93ZXI9IHlfbG93ZXIgKyBkZWx0YSwgeV91cHBlcj15X3VwcGVyICsgZGVsdGEpCiAgc21vb3Roc19kZiA9IHJiaW5kKHNtb290aHNfZGYsIGdhbV9zbW9vdGhzICU+JSBtdXRhdGUocHN5Y2hvbWV0cmljID0gbSkpCn0KCmBgYAoKIyMjIEdldCBEZW5zaXR5IERhdGEKCmBgYHtyfQpnZXRfZF9wb2ludHMgPSBmdW5jdGlvbihkZikgewogICAgeCA9IGRlbnNpdHkoZGYkc3VycCkkeAogICAgeSA9IGRlbnNpdHkoZGYkc3VycCkkeQogICAgcmV0dXJuKGRhdGEuZnJhbWUoeCwgeSkpCiAgfQoKZGVuc2l0eV9kYXRhID0gZGF0YS5mcmFtZSgpCgpmb3IobSBpbiBjKCJnYXplX2R1cmF0aW9uIiwgInRvdGFsX2R1cmF0aW9uIiwgImdvX3Bhc3RfdGltZSIsICJmaXJzdF9kdXJhdGlvbiIpKSB7CiAgZHVtbXlfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSBtKSAlPiUKICAgICAgZG8oe2dldF9kX3BvaW50cyguKX0pICU+JQogICAgICBmaWx0ZXIoeD4wLCB4PDIwKQogIGRlbnNpdHlfZGF0YSA9IHJiaW5kKGRlbnNpdHlfZGF0YSwgZHVtbXlfZGYgJT4lIG11dGF0ZShtZXRyaWM9bSkpCn0KCmBgYAoKCmBgYHtyfQoKIyBTdXJwcmlzYWwgY3VydmVzCiAgZ2dwbG90KCkgKwogICAgICAjIERlbnNpdHkgRGF0YQogICAgICBhbm5vdGF0ZSgicmVjdCIsIHhtaW49MCwgeG1heD0yMCwgeW1pbj0tMjAseW1heD0tMTAsIGZpbGw9IiNmNGY0ZjQiLCBjb2xvcj0iZ3JleSIsIGFscGhhPTEsIHNpemUgPSAwKSArCiAgICAgIGdlb21fbGluZShkYXRhID0gZGVuc2l0eV9kYXRhLCBhZXMoeD14LCB5PXkqNTAgLSAxOCksIGNvbG9yPSIjYWFhYWFhIiwgc2l6ZSA9IDAuNCkgKwogICAgICAjIFN1cnJwIC8gUnQgZGF0YQogICAgICAjZ2VvbV9saW5lKGRhdGEgPSBzbW9vdGhzX2RmLCBhZXMoeD1wcmV2X3N1cnAsIHk9eSwgY29sb3IgPSBwc3ljaG9tZXRyaWMpLCBzaXplPTAuNykgKwogICAgICBnZW9tX2xpbmUoZGF0YSA9IHNtb290aHNfZGYsIGFlcyh4PXN1cnAsIHk9eSwgY29sb3IgPSBwc3ljaG9tZXRyaWMpLCBzaXplPTAuNykgKwogICAgICAjZ2VvbV9yaWJib24oZGF0YSA9IHNtb290aHNfZGYsIGFlcyh4PXByZXZfc3VycCwgeW1pbj15X2xvd2VyLCB5bWF4PXlfdXBwZXIsIGZpbGwgPSBwc3ljaG9tZXRyaWMpLCBhbHBoYT0wLjMsIHNpemU9MC41KSArCiAgICAgIGdlb21fcmliYm9uKGRhdGEgPSBzbW9vdGhzX2RmLCBhZXMoeD1zdXJwLCB5bWluPXlfbG93ZXIsIHltYXg9eV91cHBlciwgZmlsbCA9IHBzeWNob21ldHJpYyksIGFscGhhPTAuMywgc2l6ZT0wLjUpICsKICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1jKDAsIDEwLCAyMCksIGJyZWFrcz1jKDAsIDEwLCAyMCksIG1pbm9yX2JyZWFrcyA9IE5VTEwpICsKICAgICAgZmFjZXRfd3JhcChwc3ljaG9tZXRyaWN+LiwgbnJvdyA9IDEpICsKICAgICAgeWxhYigiU2xvd2Rvd24gZHVlIHRvIFN1cnByaXNhbCAobXMpIikgKwogICAgICB4bGFiKCJTdXJwcmlzYWwgb2YgV29yZCIpICsKICAgICAgZ2d0aXRsZSgiTW9UUiBUaW1lcyBhbmQgQ3VycmVudCBXb3JkIFN1cnByaXNhbCIpCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpCiAgKQogIAojIGdnc2F2ZSgiLi4vdmlzdWFsaXphdGlvbi9zdXJwcmlzYWxfcnRfbGluay5wbmciLCBkZXZpY2UgPSAicG5nIiwgd2lkdGggPSA2LCBoZWlnaHQgPSAyLjUpCgoKYGBgCgojIyMgZm9yIHByZXZpb3VzIHdvcmQ6CgpgYGB7cn0KZml0X2dhbV9pbm5lcl8yID0gZnVuY3Rpb24oYm9vdHN0cmFwX3NhbXBsZSwgbWVhbl9wcmVkaWN0b3JzKSB7CiAgCiAgZGYgPSBib290c3RyYXBfc2FtcGxlJGRhdGEKICB3ZWlnaHRzID0gdGFidWxhdGUoYXMuaW50ZWdlcihib290c3RyYXBfc2FtcGxlKSwgbnJvdyhkZikpCiAgCiAgbSA9IGdhbShwc3ljaG9tZXRyaWMgfiBzKHN1cnAsIGJzID0gJ2NyJywgayA9IDYpICsgcyhwcmV2X3N1cnAsIGJzID0gJ2NyJywgayA9IDYpICsgdGUoZnJlcSwgbGVuLCBicyA9ICdjcicpICsgdGUocHJldl9mcmVxLCBwcmV2X2xlbiwgYnMgPSAnY3InKSwgZGF0YSA9IGRmLCB3ZWlnaHRzID0gd2VpZ2h0cykKICB0ZXJtc190b19wcmVkaWN0ID0gYygicyhzdXJwKSIsICJzKHByZXZfc3VycCkiKQogIAogIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHN1cnA9bWVhbl9wcmVkaWN0b3JzJHN1cnAsIHByZXZfc3VycD1zZXEoMCwyMCxieT0wLjEpLAogICAgICAgICAgICAgICAgICAgICAgIGZyZXE9bWVhbl9wcmVkaWN0b3JzJGZyZXEsIHByZXZfZnJlcT1tZWFuX3ByZWRpY3RvcnMkZnJlcSwKICAgICAgICAgICAgICAgICAgICAgICBsZW49bWVhbl9wcmVkaWN0b3JzJGxlbiwgcHJldl9sZW49bWVhbl9wcmVkaWN0b3JzJGxlbikKCiAgIyBSZXR1cm5zIGEgbWF0cml4IE5fc2FtcGxlcyAqIE5fdGVybXMuCiAgcGVyX3Rlcm1fcHJlZGljdGlvbnMgPSBwcmVkaWN0KG0sIG5ld2RhdGE9bmV3ZGF0YSwgdGVybXM9dGVybXNfdG9fcHJlZGljdCwgdHlwZT0idGVybXMiKQoKICAjIEFkZGl0aXZlIG1vZGVsIC0tIHN1bSBhY3Jvc3MgcHJlZGljdG9yIHJlc3BvbnNlIGNvbnRyaWJ1dGlvbnMgKG1hdHJpeCBjb2x1bW5zKS4KICBwcmVkaWN0aW9ucyA9IHJvd1N1bXMocGVyX3Rlcm1fcHJlZGljdGlvbnMpCgogIHJldHVybihuZXdkYXRhICU+JSBtdXRhdGUoeT1wcmVkaWN0aW9ucykpCn0KCmZpdF9nYW1fMiA9IGZ1bmN0aW9uKGRmLCBtZWFuX3ByZWRpY3RvcnMsIGFscGhhPTAuMDUpIHsKICAjIEJvb3RzdHJhcC1yZXNhbXBsZSBkYXRhCiAgYm9vdF9tb2RlbHMgPSBkZiAlPiUgYm9vdHN0cmFwcyh0aW1lcz0xMCkgJT4lIAogICAjIEZpdCBhIEdBTSBhbmQgZ2V0IHByZWRpY3Rpb25zIGZvciBlYWNoIHNhbXBsZQogICAgbXV0YXRlKHNtb290aGVkPW1hcChzcGxpdHMsIGZpdF9nYW1faW5uZXJfMiwgbWVhbl9wcmVkaWN0b3JzPW1lYW5fcHJlZGljdG9ycykpCiAgCiAgIyBFeHRyYWN0IG1lYW4gYW5kIDUlIGFuZCA5NSUgcGVyY2VudGlsZSB5LXZhbHVlcyBmb3IgZWFjaCBzdXJwcmlzYWwgdmFsdWUKICByZXN1bHQgPSBib290X21vZGVscyAlPiUgCiAgICB1bm5lc3Qoc21vb3RoZWQpICU+JSAKICAgIGRwbHlyOjpzZWxlY3QocHJldl9zdXJwLCB5KSAlPiUKICAgIGdyb3VwX2J5KHByZXZfc3VycCkgJT4lCiAgICAgIHN1bW1hcmlzZSh5X2xvd2VyPXF1YW50aWxlKHksIGFscGhhIC8gMiksIAogICAgICAgICAgICAgICAgeV91cHBlcj1xdWFudGlsZSh5LCAxIC0gYWxwaGEgLyAyKSwKICAgICAgICAgICAgICAgIHk9bWVhbih5KSkgJT4lIAogICAgdW5ncm91cCgpCiAgCiAgcmV0dXJuIChyZXN1bHQpCn0KYGBgCgpgYGB7cn0KZ2FtX21vZGVsaW5nX2RmXzIgPSBwcm92b19kZiAlPiUKICBzcHJlYWQobWVhc3VyZSwgdmFsdWUpICU+JQogICMgbXV0YXRlKGxlbiA9IG5jaGFyKHdvcmQpKSAlPiUgICMgbGVuIGhhcyBhbHJlYWR5IGV4aXN0cywgYnV0IGRvIG5vdCBjb3VudCBwdW5jdCBpbnRvIGxlbi4KICBncm91cF9ieShtZXRyaWMsIHRleHRfaWQpICU+JQogICAgYXJyYW5nZSh3b3JkX3RleHRfaWR4KSAlPiUKICAgIG11dGF0ZShwcmV2X3N1cnAgPSBsYWcoc3VycCksCiAgICAgICAgICAgcHJldl9mcmVxID0gbGFnKGZyZXEpLAogICAgICAgICAgIHByZXZfbGVuID0gbGFnKGxlbiksCiAgICAgICAgICAgcHJldl9leWV0cl92YWx1ZSA9IGxhZyhleWV0cl92YWx1ZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgcmVuYW1lKHBzeWNob21ldHJpYyA9IG1vdHJfdmFsdWUpCgpzbW9vdGhzX2RmID0gZGF0YS5mcmFtZSgpCgptZXRyaWNzID0gYygiZ2F6ZV9kdXJhdGlvbiIsICJ0b3RhbF9kdXJhdGlvbiIsICJnb19wYXN0X3RpbWUiLCAiZmlyc3RfZHVyYXRpb24iKQpmb3IgKG0gaW4gbWV0cmljcykgewogIHByaW50KHBhc3RlMCgiRml0dGluZyBtb2RlbCBmb3IgIiwgbSkpCiAgZHVtbXlfZGYgPSBnYW1fbW9kZWxpbmdfZGZfMiAlPiUgZmlsdGVyKG1ldHJpYyA9PSBtKQogIG1lYW5fcHJlZGljdG9ycyA9IGR1bW15X2RmICU+JSBzdW1tYXJpc2Uoc3VycCA9IG1lYW4oc3VycCksIGxlbiA9IG1lYW4obGVuKSwgZnJlcSA9IG1lYW4oZnJlcSkpCiAgc21vb3RocyA9IGR1bW15X2RmICU+JSBmaXRfZ2FtXzIoLiwgbWVhbl9wcmVkaWN0b3JzKQogICNGaXggMCBzdXJwcmlzYWwgPSAwIG1zCiAgZ2FtX3Ntb290aHMgPSBzbW9vdGhzICU+JSBtdXRhdGUoZGVsdGEgPSAwIC0geVsxXSwgeT15ICsgZGVsdGEsIHlfbG93ZXI9IHlfbG93ZXIgKyBkZWx0YSwgeV91cHBlcj15X3VwcGVyICsgZGVsdGEpCiAgc21vb3Roc19kZiA9IHJiaW5kKHNtb290aHNfZGYsIGdhbV9zbW9vdGhzICU+JSBtdXRhdGUocHN5Y2hvbWV0cmljID0gbSkpCn0KYGBgCgpgYGB7cn0KZ2V0X2RfcG9pbnRzID0gZnVuY3Rpb24oZGYpIHsKICAgIHggPSBkZW5zaXR5KGRmJHN1cnApJHgKICAgIHkgPSBkZW5zaXR5KGRmJHN1cnApJHkKICAgIHJldHVybihkYXRhLmZyYW1lKHgsIHkpKQogIH0KCmRlbnNpdHlfZGF0YSA9IGRhdGEuZnJhbWUoKQoKZm9yKG0gaW4gYygiZ2F6ZV9kdXJhdGlvbiIsICJ0b3RhbF9kdXJhdGlvbiIsICJnb19wYXN0X3RpbWUiLCAiZmlyc3RfZHVyYXRpb24iKSkgewogIGR1bW15X2RmID0gcHJvdm9fZGYgJT4lIGZpbHRlcihtZXRyaWMgPT0gbSkgJT4lCiAgICAgIGRvKHtnZXRfZF9wb2ludHMoLil9KSAlPiUKICAgICAgZmlsdGVyKHg+MCwgeDwyMCkKICBkZW5zaXR5X2RhdGEgPSByYmluZChkZW5zaXR5X2RhdGEsIGR1bW15X2RmICU+JSBtdXRhdGUobWV0cmljPW0pKQp9CmBgYAoKCmBgYHtyfQojIFN1cnByaXNhbCBjdXJ2ZXMKICBnZ3Bsb3QoKSArCiAgICAgICMgRGVuc2l0eSBEYXRhCiAgICAgIGFubm90YXRlKCJyZWN0IiwgeG1pbj0wLCB4bWF4PTIwLCB5bWluPS0yMCx5bWF4PS0xMCwgZmlsbD0iI2Y0ZjRmNCIsIGNvbG9yPSJncmV5IiwgYWxwaGE9MSwgc2l6ZSA9IDApICsKICAgICAgZ2VvbV9saW5lKGRhdGEgPSBkZW5zaXR5X2RhdGEsIGFlcyh4PXgsIHk9eSo1MCAtIDE4KSwgY29sb3I9IiNhYWFhYWEiLCBzaXplID0gMC40KSArCiAgICAgICMgU3VycnAgLyBSdCBkYXRhCiAgICAgIGdlb21fbGluZShkYXRhID0gc21vb3Roc19kZiwgYWVzKHg9cHJldl9zdXJwLCB5PXksIGNvbG9yID0gcHN5Y2hvbWV0cmljKSwgc2l6ZT0wLjcpICsKICAgICAgZ2VvbV9yaWJib24oZGF0YSA9IHNtb290aHNfZGYsIGFlcyh4PXByZXZfc3VycCwgeW1pbj15X2xvd2VyLCB5bWF4PXlfdXBwZXIsIGZpbGwgPSBwc3ljaG9tZXRyaWMpLCBhbHBoYT0wLjMsIHNpemU9MC41KSArCiAgICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHM9YygwLCAxMCwgMjApLCBicmVha3M9YygwLCAxMCwgMjApLCBtaW5vcl9icmVha3MgPSBOVUxMKSArCiAgICAgIGZhY2V0X3dyYXAocHN5Y2hvbWV0cmljfi4sIG5yb3cgPSAxKSArCiAgICAgIHlsYWIoIlNsb3dkb3duIGR1ZSB0byBTdXJwcmlzYWwgKG1zKSIpICsKICAgICAgeGxhYigiU3VycHJpc2FsIG9mIFdvcmQiKSArCiAgICAgIGdndGl0bGUoIk1vVFIgVGltZXMgYW5kIFByZXZpb3VzIFdvcmQgU3VycHJpc2FsIikKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkKICApCmBgYAoKIyMgUHJlY2lzaW9uIGFuZCBSZWNhbGwgZm9yIEZQUmVnCgpgYGB7cn0KRlBSZWdfZGYgPSBwcm92b19kZiAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiRlBSZWciKSAlPiUgc3ByZWFkKG1lYXN1cmUsIHZhbHVlKQpjb25mdXNpb25fbWF0cml4IDwtIHRhYmxlKEZQUmVnX2RmJG1vdHJfdmFsdWUgPiAwLCBGUFJlZ19kZiRleWV0cl92YWx1ZSA+IDApCmNvbmZ1c2lvbl9tYXRyaXgKCnRydWVfcG9zaXRpdmVzIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwgMl0KZmFsc2VfcG9zaXRpdmVzIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwgMV0KZmFsc2VfbmVnYXRpdmVzIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMSwgMl0KCnByZWNpc2lvbiA8LSB0cnVlX3Bvc2l0aXZlcyAvICh0cnVlX3Bvc2l0aXZlcyArIGZhbHNlX3Bvc2l0aXZlcykKcmVjYWxsIDwtIHRydWVfcG9zaXRpdmVzIC8gKHRydWVfcG9zaXRpdmVzICsgZmFsc2VfbmVnYXRpdmVzKQoKcHJpbnQoInByZWNpc2lvbiBvZiBNb3RyIEZQUmVnOiIpCnByaW50KHByZWNpc2lvbikKcHJpbnQoIlJlY2FsbCBvZiBNb3RyIEZQUmVnOiIpCnByaW50KHJlY2FsbCkKYGBgCgoKCg==